package com.example.literj.kt.core

import com.example.literj.kt.disposables.Disposable
import com.example.literj.kt.internal.schedulers.NewThreadWorker
import com.example.literj.kt.internal.schedulers.SchedulerRunnableIntrospection
import com.example.literj.kt.plugins.RxJavaPlugins
import java.util.concurrent.TimeUnit

abstract class Scheduler {
    private var IS_DRIFT_USE_NANOTIME = java.lang.Boolean.getBoolean("rx3.scheduler.use-nanotime")

    fun scheduleDirect(run: Runnable): Disposable {
        return scheduleDirect(run, 0L, TimeUnit.NANOSECONDS)
    }

    open fun scheduleDirect(run: Runnable?, delay: Long, unit: TimeUnit?): Disposable {
        val w = createWorker()
        val decoratedRun = RxJavaPlugins.onSchedule(run)
        val task = DisposeTask(decoratedRun, w)
        w.schedule(task, delay, unit)
        return task
    }

    fun computeNow(unit: TimeUnit): Long {
        return if (!IS_DRIFT_USE_NANOTIME) {
            unit.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS)
        } else unit.convert(System.nanoTime(), TimeUnit.NANOSECONDS)
    }

    abstract fun createWorker(): Worker

    fun now(unit: TimeUnit): Long {
        return computeNow(unit)
    }

    open fun start() {}

    open fun shutdown() {}

    abstract class Worker : Disposable {
        open fun schedule(run: Runnable): Disposable {
            return schedule(run, 0L, TimeUnit.NANOSECONDS)
        }

        abstract fun schedule(run: Runnable?, delay: Long, unit: TimeUnit?): Disposable
    }

    internal class DisposeTask(
        private val decoratedRun: Runnable,
        private val w: Worker
    ) : Disposable, Runnable, SchedulerRunnableIntrospection {
        var runner: Thread? = null

        override fun run() {
            runner = Thread.currentThread()
            try {
                try {
                    decoratedRun.run()
                } catch (ex: Throwable) {
                    RxJavaPlugins.onError(ex)
                    throw ex
                }
            } finally {
                dispose()
                runner = null
            }
        }

        override fun dispose() {
            if (runner == Thread.currentThread() && w is NewThreadWorker) {
                (w as? NewThreadWorker)?.shutdown()
            } else {
                w.dispose()
            }
        }

        override fun isDisposed() = w.isDisposed()
        override fun getWrappedRunnable() = this.decoratedRun
    }
}